package hydrap2p.proxy.search;
import java.io.IOException;
import java.net.BindException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.thrift.TException;

import hydrap2p.ServiceLocation;
import hydrap2p.helper.Pinger;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.helper.ThriftServerThread;
import hydrap2p.helper.ThriftUtils;
import hydrap2p.modulemanager.ModuleControl;
import hydrap2p.proxy.manager.ProxyManager;
import hydrap2p.search.SearchQuery;
import hydrap2p.search.SearchResult;
import hydrap2p.search.Searcher;
import hydrap2p.servicemanager.NameTaken;
import hydrap2p.servicemanager.ServiceManager;

public class Server implements Searcher.Iface, ModuleControl.Iface {

	private static ServiceLocation reallocation;
	private static ThriftServerThread proxythread;
	private static ThriftServerThread controlthread;
	private static ProxyManager.Iface proxy;
	
	private Map<ServiceLocation, ServiceLocation> searchproviders;
	private Map<ServiceLocation, ServiceLocation> listeners;
	private List<Searcher.Iface> realsearchers;
	
	public static void main(String[] args)
	{
		ServiceManager.Iface manager=ServiceConnector.getServiceManager();
		proxy=ServiceConnector.getService(ProxyManager.Client.class, "ProxyManager");
		if (manager==null)
		{
			System.err.println("Couldn't connect to ServiceManager");
			return;
		}
		if (proxy==null)
		{
			System.err.println("No ProxyManager found");
			return;
		}
		try {
			reallocation=proxy.getRealServiceLocation("Searcher");
		}
		catch (TException e)
		{
			System.err.println("Could not find real Searcher");
			return;
		}
		
		Server server=new Server();
		
		try {
			proxythread = ThriftUtils.initThriftServer(new Searcher.Processor(server), null);
			proxythread.start();
		} catch (BindException e1) {
			// Could not start listening
			System.err.println("Failed to start proxy object");
			server.shutdown();
		} catch (IOException e1) {
			System.err.println("Failed to start proxy object");
			server.shutdown();
		} catch (NameTaken e1) {
			// Won't happen, not registering a name
			server.shutdown();
		}
		try {
			controlthread = ThriftUtils.initThriftServer(new ModuleControl.Processor(server), "	");
			controlthread.start();
		} catch (BindException e1) {
			// Could not start listening
			System.err.println("Failed to start control object");
		} catch (IOException e1) {
			System.err.println("Failed to start control object");
		} catch (NameTaken e1) {
			System.err.println("Failed to start control object");
		}
		System.out.println("Started Proxy Searcher at "+proxythread.getLocation().address+":"+proxythread.getLocation().port);
		try {
			proxy.setProxyServiceLocation("Searcher", proxythread.getLocation());
		}
		catch (TException e)
		{
			System.err.println("Error registering proxy location");
			proxythread.shutdown();
		} catch (NameTaken e) {
			// Already a proxy object
			System.err.println("Another proxy object is registered");
			server.shutdown();
		}
		
	}
	
	private Pinger pinger1;
	private Pinger pinger2;
	
	public void shutdown()
	{
		new Shutdown().start();
	}	

	private class MyManagerPinger extends Pinger
	{		
		public MyManagerPinger(String name, ServiceLocation location) {
			super(name, location);
		}

		@Override
		public void failure(String name) {
			new Shutdown().start();
		}
	}
	
	private class MySearcherPinger extends Pinger
	{		
		public MySearcherPinger(String name, ServiceLocation location) {
			super(name, location);
		}

		@Override
		public void failure(String name) {
			new Shutdown().start();
		}
	}
	
	private class MyListenerPinger extends Pinger
	{
		private ServiceLocation locallistener;
		public MyListenerPinger(ServiceLocation locallistener) {
			super("Listener@"+locallistener.address+locallistener.port, locallistener);
			this.locallistener=locallistener;
		}

		@Override
		public void failure(String name) {
			synchronized(listeners)
			{
				listeners.remove(locallistener);
			}
		}
	}
	private class MyProviderPinger extends Pinger
	{
		private ServiceLocation localprovider;
		public MyProviderPinger(ServiceLocation localprovider) {
			super("SearchProvider@"+localprovider.address+localprovider.port, localprovider);
			this.localprovider=localprovider;
		}

		@Override
		public void failure(String name) {
			synchronized(searchproviders)
			{
				searchproviders.remove(localprovider);
			}
		}
	}
	
	private class Shutdown extends Thread
	{
		@Override
		public void run()
		{
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// Who cares
			}
			if (proxythread!=null)
				proxythread.shutdown();
			if (pinger1!=null)
				pinger1.shutdown();
			if (pinger2!=null)
				pinger2.shutdown();
			System.err.println("An error occurred");
			System.exit(0);
		}
	}
	
	public Server()
	{
		pinger1=new MyManagerPinger("ServiceManager", ServiceConnector.getLocation("ServiceManager"));
		pinger2=new MySearcherPinger("Searcher", reallocation);
		
		realsearchers=new LinkedList<Searcher.Iface>();
		searchproviders = new TreeMap<ServiceLocation, ServiceLocation>();
		listeners = new TreeMap<ServiceLocation, ServiceLocation>();
	}
	
	private Searcher.Iface checkout() throws TException
	{
		synchronized(realsearchers)
		{
			if (realsearchers.size()>0)
				return realsearchers.remove(0);
			else
			{
				Searcher.Iface temp=ServiceConnector.getService(Searcher.Client.class, reallocation);				
				if (temp==null)
				{
					System.err.println("Failed to connect to real searcher");
					throw new TException("Could not connect to real searcher");
				}
				return temp;
			}
		}
	}
	private void checkin(Searcher.Iface connection)
	{
		synchronized(realsearchers)
		{
			if (realsearchers.size()<5)
			{
				realsearchers.add(connection);
			}
		}
	}
	
	@Override
	public void ping() {
	}

	@Override
	public List<String> supportedInterfaces() {
		return Arrays.asList("Service", "Searcher");
	}

	@Override
	public boolean addSearchResult(int arg0, SearchResult arg1) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			boolean ret=realsearcher.addSearchResult(arg0, arg1);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean deleteSearch(int arg0) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			boolean ret=realsearcher.deleteSearch(arg0);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public long download(String arg0, String arg1, int arg2, int arg3)
			throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			long ret=realsearcher.download(arg0, arg1, arg2, arg3);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public List<String> getSearchActiveProviders(int arg0) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			List<String> ret=realsearcher.getSearchActiveProviders(arg0);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public SearchQuery getSearchQuery(int arg0) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			SearchQuery ret=realsearcher.getSearchQuery(arg0);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public List<SearchResult> getSearchResults(int arg0) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			List<SearchResult> ret=realsearcher.getSearchResults(arg0);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public List<String> getSearchTotalProviders(int arg0) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			List<String> ret=realsearcher.getSearchTotalProviders(arg0);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public Map<Integer, SearchQuery> getSearches() throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			Map<Integer, SearchQuery> ret=realsearcher.getSearches();
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public List<String> listSearchProviders() throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			List<String> ret=realsearcher.listSearchProviders();
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean registerSearchProvider(ServiceLocation arg0)
			throws TException {
		try {
			synchronized(searchproviders)
			{
				ServiceLocation working=searchproviders.get(arg0);
				if (working==null)
				{
					new MyProviderPinger(arg0);
					working=proxy.openCallbackPort(arg0, reallocation);
					searchproviders.put(arg0, working);
				}
				Searcher.Iface realsearcher=checkout();
				boolean ret=realsearcher.registerSearchProvider(working);
				checkin(realsearcher);
				return ret;
			}
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean restartSearch(int arg0) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			boolean ret=realsearcher.restartSearch(arg0);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean searchFinished(int arg0, String arg1) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			boolean ret=realsearcher.searchFinished(arg0, arg1);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public int startSearch(SearchQuery arg0) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			int ret=realsearcher.startSearch(arg0);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public int startSearchSome(SearchQuery arg0, List<String> arg1)
			throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			int ret=realsearcher.startSearchSome(arg0, arg1);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean stopSearch(int arg0) throws TException {
		try {
			Searcher.Iface realsearcher=checkout();
			boolean ret=realsearcher.stopSearch(arg0);
			checkin(realsearcher);
			return ret;
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean subscribeListener(ServiceLocation arg0) throws TException {
		try {
			synchronized(listeners)
			{
				ServiceLocation working=listeners.get(arg0);
				if (working==null) {
					new MyListenerPinger(arg0);
					working=proxy.openCallbackPort(arg0, reallocation);
					listeners.put(arg0, working);
				}
				Searcher.Iface realsearcher=checkout();
				boolean ret=realsearcher.subscribeListener(working);
				checkin(realsearcher);
				return ret;
			}
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean subscribeSearchListener(ServiceLocation arg0, int arg1)
			throws TException {
		try {
			synchronized(listeners)
			{
				ServiceLocation working=listeners.get(arg0);
				if (working==null)
				{
					new MyListenerPinger(arg0);
					working=proxy.openCallbackPort(arg0, reallocation);
					listeners.put(arg0,working);
				}
				Searcher.Iface realsearcher=checkout();
				boolean ret=realsearcher.subscribeSearchListener(working, arg1);
				checkin(realsearcher);
				return ret;
			}
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean unregisterSearchProvider(ServiceLocation arg0)
			throws TException {
		try {
			synchronized(searchproviders)
			{
				ServiceLocation working=searchproviders.remove(arg0);
				if (working!=null)
				{
					Searcher.Iface realsearcher=checkout();
					boolean ret=realsearcher.unregisterSearchProvider(working);
					checkin(realsearcher);
					return ret;
				}
				return false;
			}
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean unsubscribeListener(ServiceLocation arg0) throws TException {
		try {
			synchronized(listeners)
			{
				ServiceLocation working=listeners.get(arg0);
				if (working!=null)
				{
					Searcher.Iface realsearcher=checkout();
					boolean ret=realsearcher.unsubscribeListener(working);
					checkin(realsearcher);
					return ret;
				}
				return false;
			}
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public boolean unsubscribeSearchListener(ServiceLocation arg0, int arg1)
			throws TException {
		try {
			synchronized(listeners)
			{
				ServiceLocation working=listeners.get(arg0);
				if (working!=null)
				{
					Searcher.Iface realsearcher=checkout();
					boolean ret=realsearcher.unsubscribeSearchListener(working, arg1);
					checkin(realsearcher);
					return ret;
				}
				return false;
			}
		} catch (TException e) {
			shutdown();
			throw e;
		}
	}

	@Override
	public void stop() throws TException {
		shutdown();
	}

}
